 // apache module to let you write web applications in singlog.

 // How to install this?
 // - Find a well-done installation of apache.
 // - Copy libmod_sl.so to apache modules/ directory.
 // - Insert few lines into httpd.conf (with your directory names):
 //   LoadModule singlog_module modules/libmod_sl.so
 //   slUriPath /singlog
 //   slHomeDir /opt/singlog
 //   slLoadSource /ssp/web.sl
 // - Restart apache, then you can visit http://server/singlog/query

#include <string>

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"

#include "input.h"
#include "predicates.h"
#include "prover.h"
#include "system.h"
#include "try.h"

class ConfigurationRecord {
public:
  ConfigurationRecord () : uriPrefix(0) {}

  const char *uriPrefix;
  int uriPrefixLength;

  Rules rules;
  Globals globals;
};

 // Quite possible apache will run us multi-threaded;
 //  thus we store the request record in thread local storage
request_rec __thread *requestRecord;
char __thread *postedContent;
apr_table_t __thread *formData;

extern module AP_MODULE_DECLARE_DATA singlog_module;

static const char *slUriPath (cmd_parms *parms, void*, const char *uri) {
  ConfigurationRecord &conf = *(ConfigurationRecord*) ap_get_module_config (
    parms->server->module_config, &singlog_module);

  conf.uriPrefix = uri;
  conf.uriPrefixLength = strlen (uri);
  return 0;
}

static const char *slHomeDir (cmd_parms *parms, void*, const char *dir) {
  ConfigurationRecord &conf = *(ConfigurationRecord*) ap_get_module_config (
    parms->server->module_config, &singlog_module);

   // This needs to be moved into configuration record!!
  Input::singlogPath = dir;

  Trail trail;
  System loader (conf.rules, conf.globals, trail);
  loader.load ("%/auto.sl");
  return 0;
}

static const char *slLoadSource (cmd_parms *parms, void*, const char *file) {
  ConfigurationRecord &conf = *(ConfigurationRecord*) ap_get_module_config (
    parms->server->module_config, &singlog_module);

  Trail trail;
  System loader (conf.rules, conf.globals, trail);
  loader.load (file);
  return 0;
}

static command_rec moduleCommands[] = {

   // Invocation URI path prefix
  AP_INIT_TAKE1("slUriPath", (cmd_func) slUriPath, 0, RSRC_CONF, 0),

   // Where to find auto.sl and load libraries from
  AP_INIT_TAKE1("slHomeDir", (cmd_func) slHomeDir, 0, RSRC_CONF, 0),

   // Where to find other loaded files from
//  AP_INIT_TAKE1("slRunDir", (cmd_func) slRunDir, 0, RSRC_CONF, 0),

   // You may specify a source file to load, relative to library path
  AP_INIT_TAKE1("slLoadSource", (cmd_func) slLoadSource, 0, RSRC_CONF, 0),
  { 0 }
};

char *readPostedContent (request_rec *request) {
  char *data;
  if(ap_setup_client_block (request, REQUEST_CHUNKED_ERROR) != OK ||
    !ap_should_client_block (request))
    return 0;

  long length = request->remaining;
  data = (char*) apr_pcalloc (request->pool, length + 1);
  char temp[HUGE_STRING_LEN];

  long nCharsRead, pos = 0;
  while((nCharsRead =
    ap_get_client_block (request, temp, sizeof(temp))) > 0) {
    int toCopy = nCharsRead;
    if(pos + toCopy > length) toCopy = length - pos;
    memcpy (data + pos, temp, toCopy);
    pos += toCopy;
  }

  data[length] = 0;
  return data;
}

apr_table_t *decodePostedFormData (request_rec *request, char *content) {
  apr_table_t *table = apr_table_make (request->pool, 8);

  char *key, *value;
  while(*content && (value = ap_getword_nc (request->pool, &content, '&'))) {
    key = ap_getword_nc (request->pool, &value, '=');
    for(char *p = value; *p; p++) // I+love+you => I love you
      if(*p == '+') *p = ' '; // Not sure why we have to do this manually!

    ap_unescape_url (key);
    ap_unescape_url (value);
    apr_table_merge (table, key, value);
  }

  return table;
}

 // To be frank there could be memory leak now if a request is cancelled from
 //  client side. Overriding the new memory handler is not a good fix since it
 //  may corrupt the Node objects shared between requests
static int handler (request_rec *request) {
  ConfigurationRecord &conf = *(ConfigurationRecord*) ap_get_module_config (
    request->server->module_config, &singlog_module);

  if(strncmp (request->parsed_uri.path, conf.uriPrefix, conf.uriPrefixLength))
    return DECLINED; // None of our business

   // Eats up the prefix and the '/'
  char *query = request->parsed_uri.path + conf.uriPrefixLength;
  while(*query == '/') query++;
  requestRecord = request;
  postedContent = 0;
  formData = 0;
  ap_set_content_type (request, "text/html");

     // Grab posted contents. Decode if it is a submitted form
  if(request->method_number == M_POST)
    if((postedContent = readPostedContent (request)) &&
      strncasecmp (apr_table_get (request->headers_in, "Content-Type"),
      "application/x-www-form-urlencoded", 33) == 0)
    formData = decodePostedFormData (request, postedContent);

  if(!request->header_only) {
//    ap_rputs (DOCTYPE_HTML_3_2, request);

    bool result;
    Trail trail;
    System system (conf.rules, conf.globals, trail);
    pNode node = Atom::create (std::string ("web.") + query);

    Prover *prover = Prover::create (*node, system);
    try {
      result = prover->prove ();
    } catch(RaisedException ex) {
      ap_rprintf (request, "[ERROR] Caught exception: %s",
        ex.node.node->dump ().c_str ());
      result = true;
    }
    delete prover;

    if(!result) { // Sends to error log
      ap_rprintf (request, "[ERROR] Goal failed: %s\n", node.dump ().c_str ());
//      fprintf (stderr, "Goal failed: %s\n", node.display ().c_str ());
    }
  }

  return OK;
  // return DECLINED;
}

static void *moduleCreate (apr_pool_t *p, server_rec*) {
  void *ret = apr_pcalloc (p, sizeof(ConfigurationRecord));
  new (ret) ConfigurationRecord ();
  return ret;
}

static void moduleRegHooks (apr_pool_t*) {
  ap_hook_handler (handler, 0, 0, APR_HOOK_LAST);
}

 // The name of this structure is important - it must match the module name
module AP_MODULE_DECLARE_DATA singlog_module = {
  STANDARD20_MODULE_STUFF, 0, 0, moduleCreate, 0, moduleCommands, moduleRegHooks
};

class webGetForm : public OnceBindTupleProver {
  predicateName("get.form")

  bool proveOnce () {
    const char *result;
    if(formData && (
      result = apr_table_get (formData,
        tuple->nodes[1].node->display ().c_str ())
    )) {
      pNode node = new String (result);
      return binder.bind (*node, *tuple->nodes[2], system->trail);
    } else
      return false;
  }
};

ProverFactoryRegistrar<webGetForm, Tuple::ID, 3> webGetFormRegistrar;

class webGetPosted : public OnceBindTupleProver {
  predicateName("get.posted")

  bool proveOnce () {
    if(postedContent) {
      pNode node = new String (postedContent);
      return binder.bind (*node, *tuple->nodes[1], system->trail);
    } else
      return false;
  }
};

ProverFactoryRegistrar<webGetPosted, Tuple::ID, 2> webGetPostedRegistrar;

class webNl : public OnceProver {
  predicateName("wnl")

  bool proveOnce () {
    ap_rputc (10, requestRecord);
    return true;
  }

  Tuple *tuple;
  void set (Tuple &t, System&) { tuple = &t; }
};

ProverFactoryRegistrar<webNl, Atom::ID> webNlRegistrar;

class webWrite : public OnceProver {
  predicateName("www") // "Write to Worldwide Web"

  bool proveOnce () {
    for(std::vector<pNode>::iterator p =
      tuple->nodes.begin () + 1; p < tuple->nodes.end (); p++)
      ap_rputs (p->display ().c_str (), requestRecord);
    return true;
  }

  Tuple *tuple;
  void set (Tuple &t, System&) { tuple = &t; }
};

ProverFactoryRegistrar<webWrite, Tuple::ID> webWriteRegistrar;
